Skip to content

feat: add libatomic - #239

Merged
geekgonecrazy merged 23 commits into
devfrom
feat/libatomic-contract
Oct 8, 2026
Merged

geekgonecrazy merged 23 commits into
devfrom
feat/libatomic-contract

Conversation

@geekgonecrazy

@geekgonecrazy geekgonecrazy commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What

libatomic — the canonical Atomic contract and its reference implementation.

The contract

  • proto/atomic/ — the canonical sources (36 files, package atomic): shared primitives, the stable error model, pagination, the agent event envelope, and one folder per service (<svc>_service.proto for the RPCs, <svc>_messages.proto for the messages). Single source of truth — there is no second copy anywhere.
  • Codegen — build.rs compiles the tree with protox (pure-Rust protoc replacement, no system protoc) + tonic-build, emitting generated clients and servers into the atomic module (aliased proto).
  • proto/check_contract.py — the descriptor contract gate: every method declares execution_scope, allowed_caller, effect, required_capability; reads stay read-only; mutations carry and echo request metadata; retired field numbers stay reserved; workspace effects stay LOCAL; sandbox callers never receive broad authority; generation/snapshot fencing in place.

The reference handlers

src/daemon/ implements the contract's service surface as plain, transport-neutral code:

  • state.rs — the repository registry: per-repository serialization gates (redb permits one writable handle per database, so each request acquires the gate, opens, runs, drops) and read-only opens with a bounded wait, so concurrent readers coexist with short-lived writers.
  • services.rs / services_query.rs / services_agent.rs / services_provenance.rs / services_maintenance.rs — the tonic service impls and their plain inner functions: status/log/diff/conflicts/previews, working-copy and view mutations, stash, the vault entity model, attestations, knowledge graph queries, the provenance journal port (generation fencing, idempotent append by event ID, frozen-envelope paging, the stop/resume/abandon lifecycle), and repository maintenance.
  • provenance_core.rs / journal_sink.rs — the provenance journal core and its in-process sink.
  • convert.rs — domain ↔ protobuf converters.

A serving process wires the handlers onto any transport through the generated tonic servers; any Atomic-speaking consumer can call the very same handlers in-process. One implementation everywhere.

Carries two small atomic-repository additions the handlers require: in-place revise (reword) and stash support, plus the vault entity scaffolds.

The contract today

97 RPCs across 15 services, contract revision 2. Scopes: 41 LOCAL, 56 BOTH, 0 REMOTE. Sandbox-allowed methods: 32, behind an explicit allowlist.

Add-only discipline

Field and extension numbers are never reused or renumbered; removed field numbers are reserved. GetCapabilities.protocol_version is 2, and the gate pins that constant, the extension numbers (51001/51002), and the reserved ranges — an accidental renumber or reuse fails the check before anything ships.

Verification

  • proto/check_contract.py: PASS — 36 files compiled, 97 RPCs checked, all descriptor invariants green.
  • cargo clippy --workspace -- -D warnings, cargo fmt --all -- --check, cargo check --workspace: clean.
  • cargo test --workspace: 9092 passed, 0 failed.
  • Additive: no workspace crate depends on libatomic yet — the contract and handlers are available for any Atomic-speaking tooling to import.

@geekgonecrazy geekgonecrazy changed the title feat(contract): add libatomic, the canonical Atomic protobuf contract feat(contract): add libatomic Oct 5, 2026
@geekgonecrazy geekgonecrazy changed the title feat(contract): add libatomic feat: add libatomic — the Atomic contract and its reference handlers Oct 5, 2026
@geekgonecrazy geekgonecrazy changed the title feat: add libatomic — the Atomic contract and its reference handlers feat: add libatomic Oct 5, 2026
geekgonecrazy and others added 14 commits October 7, 2026 01:11
One crate owns the Atomic contract: the canonical protobuf sources
(36 files, package atomic), protox + tonic-build codegen (no system
protoc), the generated clients/servers in the atomic module (aliased
proto), and check_contract.py — the descriptor contract gate that
compiles the tree and enforces the declared invariants: scope, caller,
effect and capability annotations on every method; read/write
discipline and replay metadata echo; retired fields stay reserved;
sandbox callers never receive broad authority; generation/snapshot
fencing. PASS today at 97 RPCs across 15 services, contract revision 2.

Status: DRAFT for alignment — add-only after review and the first
tagged release. Purely additive: no consumer wiring in this change.
…the contract

The daemon module implements the contract's service surface: the
repository registry with per-repository serialization gates (one
writable database handle at a time; read-only opens with a bounded
wait so concurrent readers coexist with short-lived writers), the
tonic service impls and their plain inner functions (query, mutation,
vault, attestation, knowledge, the provenance journal port,
maintenance, view), the provenance journal core and its in-process
sink, and the domain/protobuf converters.

The handlers are transport-neutral: a serving process wires them onto
any transport through the generated servers, and any Atomic-speaking
consumer can call the same handlers in-process — one implementation.

Carries two small atomic-repository additions the handlers require:
in-place revise (reword) and stash support, plus the vault entity
scaffolds for CreateVaultEntity.
ChangeRef gains a prefix arm (whole-store case-insensitive resolution;
the target operation's own membership/guard errors speak). GetChange
carries the versioned change bundle (atomic.change.v3) plus the
provenance ledger graphs (atomic.prov.graph.v1) and the view sequence.
Sessions carry the ledger bundles (record+turns, domain-shaped JSON),
vault-derived intent counts, turn-to-intent resolution, and the session
manifest data. Status reports needs_reindex; Log filters by paths/tags
and includes inherited entries; AddFiles gains dry-run/force/directory/
no-recursive with a tolerant per-path report; Record carries an explicit
author or named identity; Remove/Move gain dry-run and force; Insert
gains the apply_dependencies escape hatch; CreateView gains the parent
(anchored, unseeded) base; SwitchView carries an explicit dirty-check
bypass; SplitView gains a dry-run analysis mode; Repair gains
REINDEX_WORKING_COPY; KnowledgeGraph enrichment scopes to explicit
changes; vault listings filter by identity.

The unrecord preview now runs the SAME domain dry-run as the mutating
unrecord — one guard surface (membership, dependents, emptiness).
GetVaultContext grows the retrieval seeds (query/intent/files/budget/
include_body, add-only) and a ranked ContextItem list: the gather+rank
behind `atomic vault context` now lives in the handler (one ranking
recipe, both transports), with the CLI rendering md/candidates/JSON from
the wire items. ListVaultEntries' Memory arm honors the identity filter —
only memories whose fresh attestation verifies under that identity list.
…ise surfaces (add-only)

Extend the wire (add-only; 97 RPCs unchanged, checker PASS) so every
remaining flag-form of the wired commands carries its data through the
handlers:

- VaultEntry carries the listing-surface columns: manifest kind, the
  attestation fresh/stale/none state, the DID-match-then-verify token,
  the memory kind/status/about columns, the whole-vault type/size/date
  columns, the goal started_at/turns, and the intent update report
  fields. ListVaultEntries gains the path-prefix, entry-type, and raw
  status filters; kind-absent is the whole-vault listing (the handler
  serves every entry, including the previously-unimplemented Goal arm).
- GetVaultEntry resolves by vault-relative path and carries the
  versioned entry bundle (schema atomic.vault.entry.bundle.v1): the
  stored entry's domain serialization plus the RAW attestation sources
  (the tracked entry and the legacy sidecar), so clients run their
  existing lift/classification code over wire-carried inputs.
- ViewInfo carries the own/inherited change-count split (view list JSON).
- KGNode/KGEdge carry the full domain fields (source, metadata);
  KgSearchQuery gains the kind filter and the candidate pool;
  NeighborsQuery gains the traversal depth.
- ListAttestations resolves one attestation by hash/prefix (the
  atomic.attestation.detail.v1 bundle: domain payload + per-view
  coverage) and serves the AI provenance summary
  (atomic.provenance.summary.v1) for the --summary/--pending modes.
- GetChange reconstructs per-file before/after content server-side (the
  legacy no-file_ops diff fallback and the FileOps context padding).
- ReviseRequest carries the content-modification mode (message, author
  override, positional paths) and the reword-form author; the domain
  gains revise_content — the unrecord/record/re-apply stack surgery.
- InsertChanges honors apply_dependencies across all source arms (the
  --deps=false opt-out); UpdateVaultEntity gains the composed multi-
  field update arm applied atomically.
- Repository::find_root accepts sandbox working trees (the .atomic-
  sandbox pointer), so service-layer resolution handles sandboxed cwd
  invocations the same way the CLI's root finder always has.
…restore single-file tails (add-only)

Extend the wire (add-only; 97 RPCs unchanged, checker PASS) so the last
local-dispatch tails of the wired commands route through the handlers:

- InsertChangesRequest gains dry_run (the handler previews every arm's
  plan with the same domain call the local bodies make), tag_from_view
  (the `insert tag --from-view` source override), the promote arm
  PromoteCurrentView (the bare promotion resolves the current view, its
  parent, the missing set, and the shared-to-shared confirm gate
  server-side — the CLI holds no repository handle), the change_set arm
  (multi-pick: raw change references resolved handler-side with the
  insert resolver's full-parse/prefix/ambiguity semantics, applied with
  cherry-pick), and the single_ref arm (a raw single reference, applied
  with the single-insert semantics honoring --deps=false).
- InsertChangesResponse carries the report fields the local renders
  need: the resolved source/target views, the confirm gate, the resolved
  change, the post-insert state, the skipped count, the materialize
  counts, the outcome conflict flag, and the still-on-disk conflict
  listing with kind/line/sides (per record, in file order).
- PreviewRestoreRequest gains content_path: the single-file dry-run's
  pristine-bytes dump — the handler reads the file's content with the
  same domain call; PreviewMutationResponse carries pristine_content
  (absent = no pristine content, the CLI's not-found error).
…t/triage-candidates/session and KG surfaces (add-only)

Tags (TagService handlers: create/delete/list/show with the full record
on the wire), provenance trace/show (ExportProvenance with the resolved
change + per-graph trace bundles), the remote registry (ListRemotes +
ManageRemotes with the add-default flag), push/pull (the network sync
runs handler-side with the client's auth headers; the structured
reports + stage errors carry the local renders), the sandbox trees
(create/stage/seal), the vault surfaces (InitVault, ExportVault,
DeleteVaultEntity, LinkVaultEntities, goal start/stop/resume, the
memory write, the tool-result previews), triage candidates (the view
resolution + candidate-set domain call), session fork/rebuild, and the
KG extras (graph/callers/plan/ask/embed over QueryGraph +
MaintainKnowledgeGraph, plus the CandidateSet Deserialize derives).
New RPC: ExplainTurns (fully annotated). Checker PASS (98 RPCs).
- state.change_store: ensure_database (the legacy-layout merge) then
  open_existing on the merged atomic.redb — redb refuses a second
  concurrent handle, so the journal-port handlers keep the sequential
  open/use/drop discipline under the gate.
- DirectJournalSink holds NO handle: each journal operation opens the
  merged database, runs, and drops it (the orchestrator opens the same
  file; the two can no longer coexist as concurrent handles).
- atomic-repository: sync the #230 database module (pub database mod for
  the CLI's root-finder predicate) + the CandidateSet/BaggageEntry/
  Coverage Deserialize derives for the triage wire.
#230 merged pristine.redb and changes.redb into a single atomic.redb,
so a fresh init no longer writes pristine.redb — but the CLI's root
finder still required it and could not discover #230-created repos
at all. The finder now applies the same predicate
Repository::find_root uses (has_database: atomic.redb, or the legacy
pristine.redb before the merge), and the discovery integration test
asserts the merged database file.
@geekgonecrazy
geekgonecrazy force-pushed the feat/libatomic-contract branch from 0455f4f to 8059551 Compare October 7, 2026 06:12
…e layer

The complete routing story the contract carries: every command family
routes through the libatomic handlers (local in-process backend by
default, reactor RPC when ATOMIC_SERVICE=reactor), with the render
parity kept in the CLI over wire data.

- atomic-cli: the rpc.rs hooks + src/service dispatch for every
  wired command (tags, provenance, remote registry, push/pull, sandbox,
  top-level split, agent explain, intent delete/link, memory write,
  vault materialize/summaries/init, vault goal lifecycle, triage
  candidates, session fork/rebuild, query KG extras, project init)
- owner.rs and its integration suite removed: the database-owner
  command is dismantled by this port
- routing parity tests for the newly wired surfaces
- provenance_rpc_sink: resolve the Reactor checkout via
  ATOMIC_REACTOR_ROOT, and skip the suite when no Reactor checkout
  exists (CI runners) — the daemon binary is not buildable from this
  workspace alone
- atomic-client: the socket-path crate the CLI service routing shares
- workspace Cargo.toml/lock: the atomic-client member
The tonic client surface returns tonic::Status (176 bytes) from
nearly every method — same opt-out the libatomic daemon surface
carries.
…ctors

Same rationale as the transport crate: the collectors propagate
tonic::Status (176 bytes) — the wire type every reactor call returns.
connect_channel is the socket connect: unix-only by construction. The
non-unix arm keeps the crate's API shape (callers compile) and refuses
connects with an explicit unsupported error.
The draft-view model filters inherited entries from plain 'atomic
log' by design (a session draft shows the work it added), but the
--all flag was still documented as 'currently a no-op' and hidden
from help — stale from before the view-filter restructure. Unhide
it and document what it actually does: the full ancestor chain.
vinceblock99 and others added 2 commits October 8, 2026 15:36
* feat(canonical): sign attestations with a key held elsewhere

`attest_value` splits into `prepare_attestation` (author, content hash and
the exact bytes to sign) and `attach_proof`, so a signer that keeps its key
outside this process — a browser's WebCrypto, a hardware token — produces
the same `eddsa-jcs-2022` attestation the CLI does. `attest_value` is now
those two halves around a local `Signer`.

`atomic-canonical-wasm` exposes that to the browser: prepare, attach and
verify attestations, plus the DID and canonical JSON of a document, all
over JSON strings and byte arrays. `build.sh` builds it with wasm-bindgen;
`smoke.mjs` signs with a non-extractable WebCrypto key, verifies, and
checks tampering is caught.

(cherry picked from commit e46c8a8)

* feat(intent): attest with a key held elsewhere

`atomic intent attest --prepare` prints the document an attestation
signs and the exact bytes to sign, needing only the identity's public
key. `--signed <file>` records an attestation a key holder produced from
it: it must be signed by `--identity`'s key and attest the intent as it
is now, so a signature over a stale or altered intent is refused.

This lets a sandbox that holds only an agent's public identity attest as
that agent, with the key kept by a signing service outside it.

(cherry picked from commit c643ed7)

* feat(token): name the server a token is for (aud)

Self-signed request tokens now carry `aud`: the bare server URL they
were minted for, normalised. A server that checks it refuses a token
minted for somewhere else, so one leaked from one server can't be
replayed at another within its five minutes. Verifiers that ignore
unknown claims are unaffected.

(cherry picked from commit d06dee6)

* feat(repository): render a view's tree without touching disk

`Repository::materialize_view_entries` hands each entry of a view — path,
inode, kind, mode, bytes, content hash, conflict-marker line — to a
sink, in path order with directories first, plus the view's Merkle state.
Read-only: no working tree, stat cache or conflict state is written, so
it runs beside other readers. It serves a remote sandbox its tree and
baseline.

The per-file rendering is now one function, `render_view_file`, shared
with `materialize_parallel`, which keeps writing to disk as before.

(cherry picked from commit 238384f)

* Never treat a sandbox's pointer as untracked

`.atomic-sandbox` showed up as untracked inside a sandbox, so
`record --all` would record it; a remote pointer carries a token.
Ignore it, and the sandbox's local cache `.atomic-sandbox.d`, like
`.atomic`.

(cherry picked from commit ac56c30)

* Draft views: render them as themselves

A draft's structural changes (files it adds, moves, deletes) wait in the
deferred tree journal until the view is checked out; TREE is the
checked-out view's. Rendering any other view — materialize_view_entries,
so every caller of it — now projects that view's journal in a write
transaction that is thrown away. Before, a file added on a draft view
never appeared on it.

The vault-indexing half of the original commit served the remote-sandbox
cache and is not carried over.

(cherry picked from commit 081e6eb)

* Stage and seal a view as it renders

materialize_view_to (behind `sandbox stage` and `seal`) listed files
from TREE, which is the checked-out view's: a file another view added
was missing from its image. It now writes materialize_view_entries,
which projects the view's own tree.

(cherry picked from commit 76aef1d)

* docs(storage): the endianness rule, written down at the encoders

A key that gets range-scanned is big-endian, so its bytes sort in the
same order as the numbers they encode; a value that is only ever looked
up by its exact key can be little-endian. The two rules meet in files
that handle both, so decode an id with that type's own `from_bytes`
rather than slicing bytes by hand — a mis-decoded id is indistinguishable
from an absent one at runtime. Recorded at the encoders in
`pristine/tables.rs` and as a section in AGENTS.md.

Also carries two fixes to the in-memory view render from the original
commit: `view_tree_items` fell through to `TREE` — the *current* view's —
when it could not open a write transaction to project another view's, so
a read-only repository answered a question about one view with another's
tree; it now refuses. And its test.

(cherry picked from commit 3e7ca25)

* refactor(wasm): the wasm crate is atomic-wasm

atomic-canonical-wasm named the crate it wraps twice over — atomic, twice —
and it is the only wasm crate in the workspace, so the extra precision cost
a word and bought nothing. atomic-wasm says the same thing in half the
length, and the module a page imports becomes atomic_wasm.

atomic-canonical itself keeps its name: that is the crate this one wraps,
and it is a dependency, not a rename of it.

Verified: builds for the host and for wasm32-unknown-unknown (787 KB
artifact at the path build.sh expects), full workspace suite 9088 tests
across 57 binaries, 0 failures.

(cherry picked from commit 9b7defc)

* test(repository): is the pristine's file lock exclusive across processes?

One test binary, two roles: the parent holds a Repository open and re-runs
itself as the child; the child tries to open the same repository and
reports what happened. Whether the lock is cross-process-exclusive is
an assumption every single-writer consumer (the CLI's bounded open-wait,
any daemon-side gate) relies on, so the answer is recorded as a test.

Carried over from the remote-sandbox branch, where measuring how far the
owner could go under concurrent sandboxes made the question load-bearing.

(cherry picked from commit 677d300)

* chore: workspace lockfile for the external-signing and view-render deps

* feat(libatomic): attest through the service with a key held elsewhere

PrepareAttestation was unimplemented and RecordAttestation loaded a
secret key and signed server-side, so an identity whose key is held
elsewhere (a browser, a hardware token, a signing service) could not
attest over the service layer — and any caller could mint an
attestation under the daemon's default human key. The CLI's
`intent attest --prepare/--signed` routed there, so the external-signer
feature the surface was built for failed on the service path.

- PrepareAttestation (intents): returns the document and the exact
  bytes a signer elsewhere must sign — the same preparation the local
  body computes — with only the identity's public key, gated on the
  pre-attest violations signing cannot fill.
- RecordAttestation (intents): with a caller-supplied signature, no
  secret key is loaded. The signature must verify against the target
  AS IT IS NOW (a stale or altered intent hashes differently, so an old
  signature cannot land), the identity must be named — an unbound
  caller cannot borrow the daemon's default human key — and the proof
  attached is exactly the one the signature earned. Without a
  signature, plain attest still signs locally, as before.
- The CLI routes `--prepare`/`--signed` through the service layer and
  prints the same JSON the local path prints.
- `intent|memory validate --json` over the service path prints the
  same top-level report shape the local body prints (the wire carries
  each violation as one message string, so an entry is its message).

This is the contract's attestation semantics (CONTRACT "Sandbox and
vault writes": preparation returns public signing bytes under a read
capability; recording verifies the supplied signature against the
current target).
@geekgonecrazy
geekgonecrazy merged commit 4b143df into dev Oct 8, 2026
7 of 8 checks passed
@geekgonecrazy
geekgonecrazy deleted the feat/libatomic-contract branch October 8, 2026 20:17
geekgonecrazy added a commit that referenced this pull request Oct 8, 2026
Since #239 every command routes through the service area (local
in-process by default), and several bridges rendered thinner output
than the local code they replaced: diff printed bare patch text with
no headers/colors/stat-graph/name-status, and log dropped the styled
hash/author/date, the (tag) marker, entry separators, and the versioned
JSON document.

DiffChunk now carries the file status and the recorded/working-copy
contents (add-only wire fields, populated by both the working-copy and
view-pair arms), so the CLI rebuilds the same FileDiffs the local path
built and renders through the SAME formatters — print_unified,
print_stat (graph bars + summary), print_name_only, print_name_status,
the word-diff pairing, and the versioned JSON document. /dev/null sides
keep their bare form in headers. log maps the wire entries onto
JsonLogEntry and reuses format_hash_with_length/format_timestamp and
the output styles, including the (tag) marker and blank-line entry
separators.
geekgonecrazy added a commit that referenced this pull request Oct 8, 2026
Since #239 every command routes through the service area (local
in-process by default), and several bridges rendered thinner output
than the local code they replaced: diff printed bare patch text with
no headers/colors/stat-graph/name-status, and log dropped the styled
hash/author/date, the (tag) marker, entry separators, and the versioned
JSON document.

DiffChunk now carries the file status and the recorded/working-copy
contents (add-only wire fields, populated by both the working-copy and
view-pair arms), so the CLI rebuilds the same FileDiffs the local path
built and renders through the SAME formatters — print_unified,
print_stat (graph bars + summary), print_name_only, print_name_status,
the word-diff pairing, and the versioned JSON document. /dev/null sides
keep their bare form in headers. log maps the wire entries onto
JsonLogEntry and reuses format_hash_with_length/format_timestamp and
the output styles, including the (tag) marker and blank-line entry
separators.
geekgonecrazy added a commit that referenced this pull request Oct 8, 2026
* fix(cli): render view list hierarchy over RPC

The #194 tree renderer only covered the local path; when a service
session is active, view list went through the RPC path's flat renderer
(no hierarchy, no counts, no state, no empty-view hiding). Map the wire
ViewInfo into ViewEntry and reuse compute_visibility/tree_order/
render_line so both paths render identically. -s and -a are now
honored over RPC too.

* fix(cli): restore pre-libatomic rendering for diff and log over RPC

Since #239 every command routes through the service area (local
in-process by default), and several bridges rendered thinner output
than the local code they replaced: diff printed bare patch text with
no headers/colors/stat-graph/name-status, and log dropped the styled
hash/author/date, the (tag) marker, entry separators, and the versioned
JSON document.

DiffChunk now carries the file status and the recorded/working-copy
contents (add-only wire fields, populated by both the working-copy and
view-pair arms), so the CLI rebuilds the same FileDiffs the local path
built and renders through the SAME formatters — print_unified,
print_stat (graph bars + summary), print_name_only, print_name_status,
the word-diff pairing, and the versioned JSON document. /dev/null sides
keep their bare form in headers. log maps the wire entries onto
JsonLogEntry and reuses format_hash_with_length/format_timestamp and
the output styles, including the (tag) marker and blank-line entry
separators.

* fix(daemon): give every redb open the cross-process grace period

The #230 merged-database discipline opens the store per operation, but
the provenance journal's opens (DirectJournalSink::store, the hook
path) and the daemon's change_store/repository accessors called redb
directly with no wait — a short-lived CLI/agent process holding
atomic.redb failed the hook's journal write on the first contested
open while repository opens rode out the hold.

- RedbChangeStore::open_existing_wait: bounded retry on
  DatabaseAlreadyOpen, other errors immediate — the same policy
  Repository::wait_for_database applies
- The open budget is now one shared, env-overridable knob:
  atomic_repository::database_lock_wait() (ATOMIC_DB_LOCK_WAIT_MS,
  30s default) — the hook-side budget wait_budget::database_wait()
  already used; libatomic's daemon opens delegate to it too
- RepoHandle::{repository,repository_readonly,change_store} and
  DirectJournalSink::store all wait; compact keeps its own retry loop
- Tests: held-handle-then-release succeeds; held-past-deadline returns
  busy in bounded time

* fix(test): format daemon/cli sources and pin contention budgets in tests

- cargo fmt over the rendering/grace-period changes (CI Format check)
- ordinary_read_timeout pins ATOMIC_DB_LOCK_WAIT_MS so the test
  measures the bound rather than the 30s default budget
- corruption-failure deadline raised for slow process startup on
  loaded machines (a contention wait would be 30s, so 20s still
  proves no wait happened)

* fix(identity): resolve the default store under ATOMIC_CONFIG_DIR

The intent-attest external-signer tests redirect the config area via
ATOMIC_CONFIG_DIR and fail on Windows with 'identity not found' —
dirs::home_dir() there resolves the profile known-folder, not HOME, so
env redirection never applied. Resolve the identity store the same way
atomic-config resolves config.toml (ATOMIC_CONFIG_DIR wins, then
~/.atomic), making the documented knob cover the whole config area.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants